// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using Microsoft.Teamfoundation.Migration.VersionControl.SharePoint.Lists;
using Microsoft.TeamFoundation.Migration.Toolkit.VC;
using System.Data.SqlClient;
using System.Transactions;
using System.Xml.Serialization;
using System.Web;
using System.Diagnostics;
using System.Data;
using Microsoft.TeamFoundation.Migration.Toolkit;

namespace Microsoft.TeamFoundation.Migration.VersionControl.Wss
{
    internal delegate bool ShouldAddItem(ListItemInfo listItem);

    internal class WSSListEnumerator
    {
        public WSSListEnumerator(string library)
        {
            m_library = library;
        }

        internal ListItemChangeInfo GetChanges(Session session, ShouldAddItem addItemCallback)
        {
            updateCurrentTable(session, addItemCallback);

            ListItemChangeInfo changeInfo = getListItemChangeInfo(session);

            return changeInfo;
        }

        private static ListItemChangeInfo getListItemChangeInfo(Session session)
        {
            ListItemChangeInfo lici = new ListItemChangeInfo();

            using (SqlConnection conn = DataAccessManager.Current.GetSqlConnection())
            using (SqlCommand cmd = conn.CreateCommand())
            {
                conn.Open();

                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandText = "prc_FindWssChanges";
                cmd.Parameters.Add("@SessionId", SqlDbType.NVarChar).Value = session.Id;

                SqlDataReader sdr = cmd.ExecuteReader();

                ListItemOrdinalCache.LoadCache(sdr);

                loadAdds(sdr, lici);
                
                if (!sdr.NextResult())
                {
                    throw new MigrationException(string.Format(WSS2TFSResources.Culture, WSS2TFSResources.ResultMissing, "Edit"));
                }
                loadEdits(sdr, lici);
                
                if (!sdr.NextResult())
                {
                    throw new MigrationException(string.Format(WSS2TFSResources.Culture, WSS2TFSResources.ResultMissing, "Delete"));
                }
                loadDeletes(sdr, lici);
                
                if (!sdr.NextResult())
                {
                    throw new MigrationException(string.Format(WSS2TFSResources.Culture, WSS2TFSResources.ResultMissing, "Rename"));
                }
                loadRenames(sdr, lici);
            }

            return lici;
        }

        private static void loadRenames(SqlDataReader sdr, ListItemChangeInfo lici)
        {
            RenameListItemOrdinalCache.LoadCache(sdr);

            lici.Renames = new List<RenameListItemInfo>();

            if (sdr.HasRows)
            {
                while (sdr.Read())
                {
                    int fileid = sdr.GetInt32(ListItemOrdinalCache.FileId);
                    int latestRev = sdr.GetInt32(ListItemOrdinalCache.LatestRevision);
                    string filePath = sdr.GetString(ListItemOrdinalCache.FilePath);
                    string fileName = sdr.GetString(ListItemOrdinalCache.FileName);
                    string parentPath = sdr.GetString(ListItemOrdinalCache.ParentPath);
                    int FSObjectType = sdr.GetInt32(ListItemOrdinalCache.FSObjectType);
                    int previousVersion = sdr.GetInt32(ListItemOrdinalCache.PreviousVersion);
                    DateTime modified = sdr.GetDateTime(ListItemOrdinalCache.Modified);
                    string editor = sdr.GetString(ListItemOrdinalCache.Editor);

                    string oldFilePath = sdr.GetString(RenameListItemOrdinalCache.OldFilePath);
                    string oldFileName = sdr.GetString(RenameListItemOrdinalCache.OldFileName);
                    string oldParentPath = sdr.GetString(RenameListItemOrdinalCache.OldParentPath);

                    lici.Renames.Add(new RenameListItemInfo(
                        fileid, latestRev, filePath, fileName, parentPath, FSObjectType, modified, editor, previousVersion,
                        oldFilePath, oldFileName, oldParentPath));
                }
            }
        }

        private static void loadDeletes(SqlDataReader sdr, ListItemChangeInfo lici)
        {
            lici.Deletes = loadRowsIntoCollection(sdr);
        }

        private static void loadEdits(SqlDataReader sdr, ListItemChangeInfo lici)
        {
            lici.Edits = loadRowsIntoCollection(sdr);
        }

        static List<ListItemInfo> loadRowsIntoCollection(SqlDataReader sdr)
        {
            List<ListItemInfo> collection = new List<ListItemInfo>();

            if (sdr.HasRows)
            {
                while (sdr.Read())
                {
                    int fileid = sdr.GetInt32(ListItemOrdinalCache.FileId);
                    int latestRev = sdr.GetInt32(ListItemOrdinalCache.LatestRevision);
                    string filePath = sdr.GetString(ListItemOrdinalCache.FilePath);
                    string fileName = sdr.GetString(ListItemOrdinalCache.FileName);
                    string parentPath = sdr.GetString(ListItemOrdinalCache.ParentPath);
                    int FSObjectType = sdr.GetInt32(ListItemOrdinalCache.FSObjectType);
                    int previousVersion = sdr.GetInt32(ListItemOrdinalCache.PreviousVersion);
                    DateTime modified = sdr.GetDateTime(ListItemOrdinalCache.Modified);
                    string editor = sdr.GetString(ListItemOrdinalCache.Editor);

                    collection.Add(new ListItemInfo(fileid, latestRev, filePath, fileName, parentPath, FSObjectType, modified, editor, previousVersion));
                }
            }

            return collection;
        }

        private static void loadAdds(SqlDataReader sdr, ListItemChangeInfo lici)
        {
            lici.Adds = loadRowsIntoCollection(sdr);
        }

        internal void UpdateWssMasterTable(Session session, List<string> deleteList, List<RenameFile> renameList, List<string> addUndeleteList, List<string> otherList)
        {
            Debug.Assert(deleteList != null, "deleteList can't be null");
            Debug.Assert(renameList != null, "renameList can't be null");
            Debug.Assert(addUndeleteList != null, "addUndeleteList can't be null");
            Debug.Assert(otherList != null, "otherList can't be null");

            List<ListItemInfo> addUndeleteItems = new List<ListItemInfo>();
            List<ListItemInfo> otherItems = new List<ListItemInfo>();


            if ((otherList.Count != 0) || (addUndeleteList.Count != 0))
            {
                List<ListItemInfo> wssItems = getWssListItemInfo(session);
                
                foreach (ListItemInfo info in wssItems)
                {
                    if (otherList.Contains(info.FilePath))
                    {
                        otherItems.Add(info);
                    }
                    else if (addUndeleteList.Contains(info.FilePath))
                    {
                        addUndeleteItems.Add(info);
                    }
                }
            }

            if (addUndeleteItems.Count != 0)
            {
                using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
                {
                    using (SqlConnection conn = DataAccessManager.Current.GetSqlConnection())
                    {
                        conn.Open();
                        foreach (ListItemInfo info in addUndeleteItems)
                        {
                            updateMasterSessionRow(conn, session, info);
                        }

                        ts.Complete();
                    }
                }
            }
            
            if (renameList.Count != 0)
            {
                using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
                {
                    using (SqlConnection conn = DataAccessManager.Current.GetSqlConnection())
                    {
                        conn.Open();
                        foreach (RenameFile renameFile in renameList)
                        {
                            using (SqlCommand cmd = conn.CreateCommand())
                            {
                                cmd.CommandType = CommandType.StoredProcedure;
                                cmd.CommandText = "prc_iiUpdateWSSMasterTableWithRename";
                                cmd.Parameters.Add("@SessionId", SqlDbType.NVarChar).Value = session.Id;
                                cmd.Parameters.Add("@OldFilePath", SqlDbType.NVarChar).Value = renameFile.OldFilePath;
                                cmd.Parameters.Add("@NewFilePath", SqlDbType.NVarChar).Value = renameFile.NewFilePath;
                                cmd.Parameters.Add("@FileName", SqlDbType.NVarChar).Value = renameFile.NewFileName;
                                cmd.ExecuteNonQuery();
                            }
                        }
                        ts.Complete();
                    }
                }
            }

            if (otherItems.Count != 0)
            {
                using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
                {
                    using (SqlConnection conn = DataAccessManager.Current.GetSqlConnection())
                    {
                        conn.Open();
                        foreach (ListItemInfo info in otherItems)
                        {
                            updateMasterSessionRow(conn, session, info);
                        }

                        ts.Complete();
                    }
                }
            }

            if (deleteList.Count != 0)
            {
                using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
                {
                    using (SqlConnection conn = DataAccessManager.Current.GetSqlConnection())
                    {
                        conn.Open();
                        foreach (string deleteFile in deleteList)
                        {
                            using (SqlCommand cmd = conn.CreateCommand())
                            {
                                cmd.CommandType = CommandType.StoredProcedure;
                                cmd.CommandText = "prc_iiUpdateWSSMasterTableWithDelete";
                                cmd.Parameters.Add("@SessionId", SqlDbType.NVarChar).Value = session.Id;
                                cmd.Parameters.Add("@FilePath", SqlDbType.NVarChar).Value = deleteFile;
                                cmd.ExecuteNonQuery();
                            }
                        }
                        ts.Complete();
                    }
                }
            }
        }

        private static void updateMasterSessionRow(SqlConnection conn, Session session, ListItemInfo li)
        {
            using (SqlCommand cmd = conn.CreateCommand())
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandText = "prc_iiUpdateMasterSessionRow";
                cmd.Parameters.Add("@SessionId", SqlDbType.NVarChar).Value = session.Id;
                cmd.Parameters.Add("@FileID", SqlDbType.Int).Value = li.FileId;
                cmd.Parameters.Add("@LatestVersion", SqlDbType.Int).Value = li.LatestRevision;
                cmd.Parameters.Add("@FilePath", SqlDbType.NVarChar).Value = li.FilePath;
                cmd.Parameters.Add("@FileName", SqlDbType.NVarChar).Value = li.FileName;
                cmd.Parameters.Add("@ParentPath", SqlDbType.NVarChar).Value = li.ParentPath;
                cmd.Parameters.Add("@FSObjectType", SqlDbType.Int).Value = li.FSObjType;
                cmd.Parameters.Add("@Modified", SqlDbType.DateTime).Value = li.Modified;
                cmd.Parameters.Add("@Editor", SqlDbType.NVarChar).Value = li.Editor;
                cmd.ExecuteNonQuery();
            }
        }

        private List<ListItemInfo> getWssListItemInfo(Session session)
        {
            XmlNode viewFields = getViewFields();
            XmlNode queryOptions = getQueryOptions(null);
            List<ListItemInfo> items = new List<ListItemInfo>();

            using (Lists l = new Lists(((WssProvider)session.Source).BaseUrl))
            {
                l.UseDefaultCredentials = true;
                l.UnsafeAuthenticatedConnectionSharing = true;

                while (true)
                {
                    XmlNode node = l.GetListItems(m_library,
                        null, null, viewFields, null, queryOptions);

                    XmlSerializer xs = new XmlSerializer(typeof(listitems));
                    listitems li = (listitems)xs.Deserialize(new StringReader(node.OuterXml));

                    if (li.Items != null &&
                        li.Items.Length > 0 &&
                        li.Items[0].row != null &&
                        li.Items[0].row.Length > 0)
                    {
                        Debug.Assert(li.Items.Length == 1);

                        foreach (row r in li.Items[0].row)
                        {
                            ListItemInfo info = new ListItemInfo(r, session.Culture);
                            items.Add(info);
                        }

                        string pagingData = li.Items[0].ListItemCollectionPositionNext;

                        if (!string.IsNullOrEmpty(pagingData))
                        {
                            pagingData = HttpUtility.HtmlEncode(pagingData);
                            queryOptions = getQueryOptions(pagingData);
                        }
                        else
                        {
                            // we're at the end of the list
                            break;
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }
            return items;
        }

        private void updateCurrentTable(Session session, ShouldAddItem addItemCallback)
        {
            List<ListItemInfo> wssItems = getWssListItemInfo(session);
            List<ListItemInfo> items = new List<ListItemInfo>();

            foreach (ListItemInfo info in wssItems)
            {
                VersionControlEventArgs change = new VersionControlEventArgs(
                info.FilePath, SystemType.Other);

                session.OnAnalyzingChangeStarting(change);

                if (addItemCallback(info))
                {
                    items.Add(info);
                }

                session.OnAnalyzingChangeComplete(change);
            }

            using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
            {
                using (SqlConnection conn = DataAccessManager.Current.GetSqlConnection())
                {
                    conn.Open();

                    clearExistingCurrentRows(conn, session.Id);
                    if (items.Count > 0)
                    {
                        foreach (ListItemInfo info in items)
                        {
                            addSessionRow(conn, session, info);
                        }
                    }

                    ts.Complete();
                }
            }
        }

        private static void addSessionRow(SqlConnection conn, Session session, ListItemInfo li)
        {
            using (SqlCommand cmd = conn.CreateCommand())
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandText = "prc_iiCreateWssListItem";
                cmd.Parameters.Add("@SessionId", SqlDbType.NVarChar).Value = session.Id;
                cmd.Parameters.Add("@FileID", SqlDbType.Int).Value = li.FileId;
                cmd.Parameters.Add("@LatestVersion", SqlDbType.Int).Value = li.LatestRevision;
                cmd.Parameters.Add("@FilePath", SqlDbType.NVarChar).Value = li.FilePath;
                cmd.Parameters.Add("@FileName", SqlDbType.NVarChar).Value = li.FileName;
                cmd.Parameters.Add("@ParentPath", SqlDbType.NVarChar).Value = li.ParentPath;
                cmd.Parameters.Add("@FSObjectType", SqlDbType.Int).Value = li.FSObjType;
                cmd.Parameters.Add("@Modified", SqlDbType.DateTime).Value = li.Modified;
                cmd.Parameters.Add("@Editor", SqlDbType.NVarChar).Value = li.Editor;

                int rows = cmd.ExecuteNonQuery();

                Debug.Assert(rows == 1);
            }
        }

        private static void clearExistingCurrentRows(SqlConnection conn, string sessionId)
        {
            using (SqlCommand cmd = conn.CreateCommand())
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandText = "prc_iiClearWSSCurrentTable";
                cmd.Parameters.Add("@SessionId", SqlDbType.NVarChar).Value = sessionId;
                cmd.ExecuteNonQuery();
            }
        }

        internal static XmlNode getQueryOptions(string pagingQuery)
        {
            XmlDocument doc = new XmlDocument();

            string pagingData =
                (string.IsNullOrEmpty(pagingQuery)) ? string.Empty : "<Paging ListItemCollectionPositionNext=\"" + pagingQuery + "\" />";

            using (StringReader reader = new StringReader(
                "<QueryOptions>" +
                "   <IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns><DateInUtc>TRUE</DateInUtc><ViewAttributes Scope=\"RecursiveAll\" />" +
                pagingData +
                "</QueryOptions>"))
            {
                doc.Load(reader);
            }

            return doc.SelectSingleNode("/QueryOptions");
        }

        internal static XmlNode getViewFields()
        {
            XmlDocument doc = new XmlDocument();

            using (StringReader reader = new StringReader(
            "<ViewFields>" +
                "<FieldRef Name=\"ID\" />" +
                "<FieldRef Name=\"FSObjType\" />" +
                "<FieldRef Name=\"CheckedOutUserId\" />" +
                "<FieldRef Name=\"Modified\" />" +
                "<FieldRef Name=\"FileDirRef\" />" +
                "<FieldRef Name=\"File_x0020_Size\" />" +
                "<FieldRef Name=\"owshiddenversion\" />" +
            "</ViewFields>"))
            {
                doc.Load(reader);
            }

            return doc.SelectSingleNode("/ViewFields");
        }

        string m_library;
    }

    internal static class ListItemOrdinalCache
    {
        internal static void LoadCache(SqlDataReader sdr)
        {
            if (!s_cacheLoaded)
            {
                lock (s_cacheLock)
                {
                    if (!s_cacheLoaded)
                    {
                        s_fileId = sdr.GetOrdinal("FileID");
                        s_latestRev = sdr.GetOrdinal("LatestVersion");
                        s_filePath = sdr.GetOrdinal("FilePath");
                        s_fileName = sdr.GetOrdinal("FileName");
                        s_parentPath = sdr.GetOrdinal("ParentPath");
                        s_FSObjectType = sdr.GetOrdinal("FSObjectType");
                        s_previousVersion = sdr.GetOrdinal("PreviousVersion");
                        s_modified = sdr.GetOrdinal("Modified");
                        s_editor = sdr.GetOrdinal("Editor");

                        s_cacheLoaded = true;
                    }
                }
            }
        }

        internal static int FileId
        {
            get
            {
                demandCacheLoaded();
                return s_fileId;
            }
        }

        internal static int LatestRevision
        {
            get
            {
                demandCacheLoaded();
                return s_latestRev;
            }
        }

        internal static int FilePath
        {
            get
            {
                demandCacheLoaded();
                return s_filePath;
            }
        }

        internal static int FileName
        {
            get
            {
                demandCacheLoaded();
                return s_fileName;
            }
        }

        internal static int ParentPath
        {
            get
            {
                demandCacheLoaded();
                return s_parentPath;
            }
        }

        internal static int PreviousVersion
        {
            get
            {
                demandCacheLoaded();
                return s_previousVersion;
            }
        }

        internal static int FSObjectType
        {
            get
            {
                return s_FSObjectType;
            }
        }

        internal static int Modified
        {
            get
            {
                return s_modified;
            }
        }

        internal static int Editor
        {
            get
            {
                return s_editor;
            }
        }

        private static void demandCacheLoaded()
        {
            if (!s_cacheLoaded)
            {
                lock (s_cacheLock)
                {
                    if (!s_cacheLoaded)
                    {
                        throw new MigrationException(WSS2TFSResources.OrdinalCacheNotInitialized);
                    }
                }
            }
        }

        static bool s_cacheLoaded;
        static object s_cacheLock = new object();

        static int s_fileId;
        static int s_latestRev;
        static int s_filePath;
        static int s_fileName;
        static int s_parentPath;
        static int s_FSObjectType;
        static int s_previousVersion;
        static int s_modified;
        static int s_editor;
    }

    internal static class RenameListItemOrdinalCache
    {
        internal static void LoadCache(SqlDataReader sdr)
        {
            if (!s_cacheLoaded)
            {
                lock (s_cacheLock)
                {
                    if (!s_cacheLoaded)
                    {
                        s_oldFilePath = sdr.GetOrdinal("OldFilePath");
                        s_oldFileName = sdr.GetOrdinal("OldFileName");
                        s_oldParentPath = sdr.GetOrdinal("OldParentPath");

                        s_cacheLoaded = true;
                    }
                }
            }
        }

        internal static int OldFilePath
        {
            get
            {
                demandCacheLoaded();
                return s_oldFilePath;
            }
        }

        internal static int OldFileName
        {
            get
            {
                demandCacheLoaded();
                return s_oldFileName;
            }
        }

        internal static int OldParentPath
        {
            get
            {
                demandCacheLoaded();
                return s_oldParentPath;
            }
        }

        private static void demandCacheLoaded()
        {
            if (!s_cacheLoaded)
            {
                lock (s_cacheLock)
                {
                    if (!s_cacheLoaded)
                    {
                        throw new MigrationException(WSS2TFSResources.OrdinalCacheNotInitialized);
                    }
                }
            }
        }


        static bool s_cacheLoaded;
        static object s_cacheLock = new object();

        static int s_oldFilePath;
        static int s_oldFileName;
        static int s_oldParentPath;
    }
}
